| 1 | import type { APIContext, GetStaticPathsResult } from "astro"; |
| 2 | import { getCollection, getEntryBySlug } from "astro:content"; |
| 3 | import satori, { SatoriOptions } from "satori"; |
| 4 | import { html } from "satori-html"; |
| 5 | import { Resvg } from "@resvg/resvg-js"; |
| 6 | import siteConfig from "@/site-config"; |
| 7 | import { getFormattedDate } from "@/utils"; |
| 8 | import fs from "fs"; |
| 9 | |
| 10 | const monoFontReg = await fs.readFileSync("../../../CommitMono-400-Regular.otf") |
| 11 | |
| 12 | const monoFontBold = await fs.readFileSync("../../../CommitMono-700-Regular.otf") |
| 13 | |
| 14 | const ogOptions: SatoriOptions = { |
| 15 | width: 1200, |
| 16 | height: 630, |
| 17 | // debug: true, |
| 18 | embedFont: true, |
| 19 | fonts: [ |
| 20 | { |
| 21 | name: "Roboto Mono", |
| 22 | data: await monoFontReg, |
| 23 | weight: 400, |
| 24 | style: "normal", |
| 25 | }, |
| 26 | { |
| 27 | name: "Roboto Mono", |
| 28 | data: await monoFontBold, |
| 29 | weight: 700, |
| 30 | style: "normal", |
| 31 | }, |
| 32 | ], |
| 33 | }; |
| 34 | |
| 35 | const markup = (title: string, pubDate: string, description: string) => html`<div |
| 36 | tw="flex flex-col w-full h-full bg-[#191724] text-[#e0def4]" |
| 37 | > |
| 38 | <div tw="flex flex-col flex-1 w-full p-10 justify-center"> |
| 39 | <p tw="text-2xl mb-6">${pubDate}</p> |
| 40 | <h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1> |
| 41 | <h2 tw="text-2xl font-bold leading-snug text-white">${description}</h2> |
| 42 | </div> |
| 43 | <div tw="flex items-center justify-between w-full p-10 border-t border-[#7c6f64] text-xl"> |
| 44 | <div tw="flex items-center"> |
| 45 | |
| 46 | <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" height="60"> |
| 47 | <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z" /> |
| 48 | </svg> |
| 49 | <p tw="ml-3 font-semibold text-3xl">${siteConfig.title}</p> |
| 50 | </div> |
| 51 | </div> |
| 52 | </div>`; |
| 53 | |
| 54 | export async function get({ params: { slug } }: APIContext) { |
| 55 | const post = await getEntryBySlug("post", slug!); |
| 56 | const title = post?.data.title ?? siteConfig.title; |
| 57 | const postDate = getFormattedDate(post?.data.publishDate ?? Date.now(), { |
| 58 | weekday: "long", |
| 59 | }); |
| 60 | const description = post?.data.description ?? siteConfig.title; |
| 61 | const svg = await satori(markup(title, postDate, description), ogOptions); |
| 62 | const png = new Resvg(svg).render().asPng(); |
| 63 | return { |
| 64 | body: png, |
| 65 | encoding: "binary", |
| 66 | }; |
| 67 | } |
| 68 | |
| 69 | export async function getStaticPaths(): Promise<GetStaticPathsResult> { |
| 70 | const posts = await getCollection("post"); |
| 71 | return posts.filter(({ data }) => !data.ogImage).map(({ slug }) => ({ params: { slug } })); |
| 72 | } |